#pragma once
#include "Runtime/BattleFirePrefix.h"
#include "Float4.h"
#include "Vector.h"
#include "Float16Color.h"
#include "MathUtils.h"
namespace Alice{
    /**
     * Enum for the different kinds of gamma spaces we expect to need to convert from/to.
     */
    enum class EGammaSpace : uint8
    {
        /** No gamma correction is applied to this space, the incoming colors are assumed to already be in linear space. */
        Linear,
        /** A simplified sRGB gamma correction is applied, pow(1/2.2). */
        Pow22,
        /** Use the standard sRGB conversion. */
        sRGB,

        Invalid
    };

    struct Color;
    /**
     * A linear, 32-bit/component floating point RGBA color.
     */
    struct FLinearColor
    {
        union
        {
            struct 
            {
                float	R,
                        G,
                        B,
                        A;
            };
            float RGBA[4];
        };

        /** Static lookup table used for Color -> FLinearColor conversion. Pow(2.2) */
        static float Pow22OneOver255Table[256];

        /** Static lookup table used for Color -> FLinearColor conversion. sRGB */
        static float sRGBToLinearTable[256];

        FLinearColor() = default;
        FORCEINLINE explicit FLinearColor(EForceInit)
        : R(0), G(0), B(0), A(0)
        {}
        constexpr FORCEINLINE FLinearColor(float InR,float InG,float InB,float InA = 1.0f): R(InR), G(InG), B(InB), A(InA) {}
        
        /**
         * Converts an Color which is assumed to be in sRGB space, into linear color space.
         * @param Color The sRGB color that needs to be converted into linear space.
         * to get direct conversion use ReinterpretAsLinear
         */
        constexpr FORCEINLINE FLinearColor(const Color& Color);

        FLinearColor(const Vector3f& Vector);
        explicit FLinearColor(const Vector3d& Vector); // Warning: keep this explicit, or FVector4f will be implicitly created from FVector3d via FLinearColor

        FLinearColor(const float4& Vector);
        
        // use Float16Color::GetFloats() directly
        explicit FLinearColor(const FFloat16Color& C);

        // Conversions.
        Color ToRGBE() const;

        /**
         * Converts an Color coming from an observed sRGB output, into a linear color.
         * @param Color The sRGB color that needs to be converted into linear space.
         */
        FORCEINLINE static FLinearColor FromSRGBColor(const Color& Color)
        {
            return FLinearColor(Color);
        }

        /**
         * Converts an Color coming from an observed Pow(1/2.2) output, into a linear color.
         * @param Color The Pow(1/2.2) color that needs to be converted into linear space.
         */
        static FLinearColor FromPow22Color(const Color& Color);

        // Operators.

        FORCEINLINE float& Component(int32 Index)
        {
            return RGBA[Index];
        }

        FORCEINLINE const float& Component(int32 Index) const
        {
            return RGBA[Index];
        }

        FORCEINLINE FLinearColor operator+(const FLinearColor& ColorB) const
        {
            return FLinearColor(
                this->R + ColorB.R,
                this->G + ColorB.G,
                this->B + ColorB.B,
                this->A + ColorB.A
                );
        }
        FORCEINLINE FLinearColor& operator+=(const FLinearColor& ColorB)
        {
            R += ColorB.R;
            G += ColorB.G;
            B += ColorB.B;
            A += ColorB.A;
            return *this;
        }

        FORCEINLINE FLinearColor operator-(const FLinearColor& ColorB) const
        {
            return FLinearColor(
                this->R - ColorB.R,
                this->G - ColorB.G,
                this->B - ColorB.B,
                this->A - ColorB.A
                );
        }
        FORCEINLINE FLinearColor& operator-=(const FLinearColor& ColorB)
        {
            R -= ColorB.R;
            G -= ColorB.G;
            B -= ColorB.B;
            A -= ColorB.A;
            return *this;
        }

        FORCEINLINE FLinearColor operator*(const FLinearColor& ColorB) const
        {
            return FLinearColor(
                this->R * ColorB.R,
                this->G * ColorB.G,
                this->B * ColorB.B,
                this->A * ColorB.A
                );
        }
        FORCEINLINE FLinearColor& operator*=(const FLinearColor& ColorB)
        {
            R *= ColorB.R;
            G *= ColorB.G;
            B *= ColorB.B;
            A *= ColorB.A;
            return *this;
        }

        FORCEINLINE FLinearColor operator*(float Scalar) const
        {
            return FLinearColor(
                this->R * Scalar,
                this->G * Scalar,
                this->B * Scalar,
                this->A * Scalar
                );
        }

        FORCEINLINE FLinearColor& operator*=(float Scalar)
        {
            R *= Scalar;
            G *= Scalar;
            B *= Scalar;
            A *= Scalar;
            return *this;
        }

        FORCEINLINE FLinearColor operator/(const FLinearColor& ColorB) const
        {
            return FLinearColor(
                this->R / ColorB.R,
                this->G / ColorB.G,
                this->B / ColorB.B,
                this->A / ColorB.A
                );
        }
        FORCEINLINE FLinearColor& operator/=(const FLinearColor& ColorB)
        {
            R /= ColorB.R;
            G /= ColorB.G;
            B /= ColorB.B;
            A /= ColorB.A;
            return *this;
        }

        FORCEINLINE FLinearColor operator/(float Scalar) const
        {
            const float	InvScalar = 1.0f / Scalar;
            return FLinearColor(
                this->R * InvScalar,
                this->G * InvScalar,
                this->B * InvScalar,
                this->A * InvScalar
                );
        }
        FORCEINLINE FLinearColor& operator/=(float Scalar)
        {
            const float	InvScalar = 1.0f / Scalar;
            R *= InvScalar;
            G *= InvScalar;
            B *= InvScalar;
            A *= InvScalar;
            return *this;
        }

        // clamped in 0..1 range
        FORCEINLINE FLinearColor GetClamped(float InMin = 0.0f, float InMax = 1.0f) const
        {
            FLinearColor Ret;

            Ret.R = MathUtils::Clamp(R, InMin, InMax);
            Ret.G = MathUtils::Clamp(G, InMin, InMax);
            Ret.B = MathUtils::Clamp(B, InMin, InMax);
            Ret.A = MathUtils::Clamp(A, InMin, InMax);

            return Ret;
        }

        /** Comparison operators */
        FORCEINLINE bool operator==(const FLinearColor& ColorB) const
        {
            return this->R == ColorB.R && this->G == ColorB.G && this->B == ColorB.B && this->A == ColorB.A;
        }
        FORCEINLINE bool operator!=(const FLinearColor& Other) const
        {
            return this->R != Other.R || this->G != Other.G || this->B != Other.B || this->A != Other.A;
        }

        // Error-tolerant comparison.
        FORCEINLINE bool Equals(const FLinearColor& ColorB, float Tolerance=ALICE_KINDA_SMALL_NUMBER) const
        {
            return MathUtils::Abs(this->R - ColorB.R) < Tolerance && MathUtils::Abs(this->G - ColorB.G) < Tolerance && MathUtils::Abs(this->B - ColorB.B) < Tolerance && MathUtils::Abs(this->A - ColorB.A) < Tolerance;
        }

        FLinearColor CopyWithNewOpacity(float NewOpacicty) const
        {
            FLinearColor NewCopy = *this;
            NewCopy.A = NewOpacicty;
            return NewCopy;
        }

        //UE_DEPRECATED(4.22, "FGetHSV doesn't perform a valid HSV conversion, use MakeFromHSV8 instead")
        static FLinearColor FGetHSV(uint8 H,uint8 S,uint8 V);

        /**
         * Converts byte hue-saturation-brightness to floating point red-green-blue.
         */
        static FLinearColor MakeFromHSV8(uint8 H, uint8 S, uint8 V);

        /**
        * Makes a random but quite nice color.
        */
        static FLinearColor MakeRandomColor();

        /**
        * Converts temperature in Kelvins of a black body radiator to RGB chromaticity.
        */
        static FLinearColor MakeFromColorTemperature( float Temp );

        /**
        * Makes a random color based on a seed.
        */
        static FLinearColor MakeRandomSeededColor(int32 Seed);

        /**
         * Euclidean distance between two points.
         */
        static inline float Dist( const FLinearColor &V1, const FLinearColor &V2 )
        {
            return MathUtils::Sqrt( MathUtils::Square(V2.R-V1.R) + MathUtils::Square(V2.G-V1.G) + MathUtils::Square(V2.B-V1.B) + MathUtils::Square(V2.A-V1.A) );
        }

        /**
         * Generates a list of sample points on a Bezier curve defined by 2 points.
         *
         * @param	ControlPoints	Array of 4 Linear Colors (vert1, controlpoint1, controlpoint2, vert2).
         * @param	NumPoints		Number of samples.
         * @param	OutPoints		Receives the output samples.
         * @return					Path length.
         */
        static float EvaluateBezier(const FLinearColor* ControlPoints, int32 NumPoints, std::vector<FLinearColor>& OutPoints);

        /** Converts a linear space RGB color to an HSV color */
        FLinearColor LinearRGBToHSV() const;

        /** Converts an HSV color to a linear space RGB color */
        FLinearColor HSVToLinearRGB() const;

        /**
         * Linearly interpolates between two colors by the specified progress amount.  The interpolation is performed in HSV color space
         * taking the shortest path to the new color's hue.  This can give better results than FMath::Lerp(), but is much more expensive.
         * The incoming colors are in RGB space, and the output color will be RGB.  The alpha value will also be interpolated.
         * 
         * @param	From		The color and alpha to interpolate from as linear RGBA
         * @param	To			The color and alpha to interpolate to as linear RGBA
         * @param	Progress	Scalar interpolation amount (usually between 0.0 and 1.0 inclusive)
         * @return	The interpolated color in linear RGB space along with the interpolated alpha value
         */
        static FLinearColor LerpUsingHSV( const FLinearColor& From, const FLinearColor& To, const float Progress );

        /** Quantizes the linear color with rounding and returns the result as a Color.  This bypasses the SRGB conversion. 
        * QuantizeRound can be dequantized back to linear with Color::ReinterpretAsLinear (just /255.f)
        * this matches the GPU UNORM<->float conversion spec and should be preferred
        */
        FORCEINLINE Color QuantizeRound() const;
        
        /** Quantizes the linear color and returns the result as a Color.  This bypasses the SRGB conversion.
        * Uses floor quantization, which does not match the GPU standard conversion.
        * Restoration to float should be done with a +0.5 bias to restore to centered buckets.
        * Do NOT use this for graphics or textures or images, use QuantizeRound instead.
        */
        FORCEINLINE Color QuantizeFloor() const;

        /** Quantizes the linear color and returns the result as a Color with optional sRGB conversion. 
        * Clamps in [0,1] range before conversion.
        * ToColor(false) is QuantizeRound
        */
        Color ToColorSRGB() const;
        
        FORCEINLINE Color ToColor(const bool bSRGB) const;
        
        /**
         * Returns a desaturated color, with 0 meaning no desaturation and 1 == full desaturation
         *
         * @param	Desaturation	Desaturation factor in range [0..1]
         * @return	Desaturated color
         */
        FLinearColor Desaturate( float Desaturation ) const;

        /** Computes the perceptually weighted luminance value of a color. */
        float GetLuminance() const;
        
        /**
         * Returns the maximum value in this color structure
         *
         * @return The maximum color channel value
         */
        FORCEINLINE float GetMax() const
        {
            return MathUtils::Max( MathUtils::Max( MathUtils::Max( R, G ), B ), A );
        }

        /** useful to detect if a light contribution needs to be rendered */
        bool IsAlmostBlack() const
        {
            return MathUtils::Square(R) < ALICE_DELTA && MathUtils::Square(G) < ALICE_DELTA && MathUtils::Square(B) < ALICE_DELTA;
        }

        /**
         * Returns the minimum value in this color structure
         *
         * @return The minimum color channel value
         */
        FORCEINLINE float GetMin() const
        {
            return MathUtils::Min( MathUtils::Min( MathUtils::Min( R, G ), B ), A );
        }

        /**
         * Helper for pixel format conversions. Clamps to [0,1], mapping NaNs to 0,
         * for consistency with GPU conversions.
         * 
         * @param InValue The input value.
         * @return InValue clamped to [0,1]. NaNs map to 0.
         */
        static FORCEINLINE float Clamp01NansTo0(float InValue)
        {
            // Write this explicitly instead of using FMath::Clamp because we're particular
            // about what happens with NaNs here.
            const float ClampedLo = (InValue > 0.0f) ? InValue : 0.0f; // Also turns NaNs into 0.
            return (ClampedLo < 1.0f) ? ClampedLo : 1.0f;
        }

        /**
         * @brief Helper function to generate distinct colors from a sequence of integers where each integer increment
         * spins around the Hue wheel by increments of the golden ratio.
         * @param Seed			the "seed" for a semi-random, but deterministic color
         * @param Saturation	0-1 saturation of resulting color
         * @param Value			0-1 brightness of resulting color
         * @param HueRotation	0-360 amount to rotate around the hue wheel
         * @return color with semi-random hue */
        static FLinearColor IntToDistinctColor(
            const int32 Seed,
            const float Saturation=1.f,
            const float Value=1.f,
            const float HueRotation=180.f)
        {
            constexpr float GoldenRatio = 1.618033988749895f; // (1 + FMath::Sqrt(5.f)) / 2.f;
            return FLinearColor(static_cast<float>(Seed) * GoldenRatio * HueRotation, Saturation, Value).HSVToLinearRGB();
        }

        // Common colors.	
        static const FLinearColor White;
        static const FLinearColor Gray;
        static const FLinearColor Black;
        static const FLinearColor Transparent;
        static const FLinearColor Red;
        static const FLinearColor Green;
        static const FLinearColor Blue;
        static const FLinearColor Yellow;
    };
    //
    //	Color
    //	Stores a color with 8 bits of precision per channel.  
    //	Note: Linear color values should always be converted to gamma space before stored in an Color, as 8 bits of precision is not enough to store linear space colors!
    //	This can be done with FLinearColor::ToColor(true) 
    //

    struct Color
    {
    public:
        // Variables.
    #if PLATFORM_LITTLE_ENDIAN
        union { struct{ uint8 B,G,R,A; }; uint32 Bits; };
    #else // PLATFORM_LITTLE_ENDIAN
        union { struct{ uint8 A,R,G,B; }; uint32 Bits; };
    #endif

        uint32& DWColor(void) {return Bits;}
        const uint32& DWColor(void) const {return Bits;}

        // Constructors.
        Color() = default;
        FORCEINLINE explicit Color(EForceInit)
        {
            // put these into the body for proper ordering with INTEL vs non-INTEL_BYTE_ORDER
            R = G = B = A = 0;
        }
        constexpr FORCEINLINE Color( uint8 InR, uint8 InG, uint8 InB, uint8 InA = 255 )
            // put these into the body for proper ordering with INTEL vs non-INTEL_BYTE_ORDER
    #if PLATFORM_LITTLE_ENDIAN
            : B(InB), G(InG), R(InR), A(InA)
    #else
            : A(InA), R(InR), G(InG), B(InB)
    #endif
        {}

        FORCEINLINE explicit Color( uint32 InColor )
        { 
            DWColor() = InColor; 
        }

        // Operators.
        FORCEINLINE bool operator==( const Color &C ) const
        {
            return DWColor() == C.DWColor();
        }

        FORCEINLINE bool operator!=( const Color& C ) const
        {
            return DWColor() != C.DWColor();
        }

        FORCEINLINE void operator+=(const Color& C)
        {
            R = (uint8) MathUtils::Min((int32) R + (int32) C.R,255);
            G = (uint8) MathUtils::Min((int32) G + (int32) C.G,255);
            B = (uint8) MathUtils::Min((int32) B + (int32) C.B,255);
            A = (uint8) MathUtils::Min((int32) A + (int32) C.A,255);
        }

        FLinearColor FromRGBE() const;

        /**
         * Makes a random but quite nice color.
         */
        static Color MakeRandomColor();

        /**
         * Makes a color red->green with the passed in scalar (e.g. 0 is red, 1 is green)
         */
        static Color MakeRedToGreenColorFromScalar(float Scalar);

        /**
        * Converts temperature in Kelvins of a black body radiator to RGB chromaticity.
        */
        static Color MakeFromColorTemperature( float Temp );

        /**
        * Makes a random color based on a seed.
        */
        static Color MakeRandomSeededColor(int32 Seed);

        /**
        * Conversions to/from GPU UNorm floats, U8, U16
        * matches convention of Color FLinearColor::QuantizeRound
        */

        static uint8 QuantizeUNormFloatTo8( float UnitFloat )
        {
            return (uint8)( 0.5f + FLinearColor::Clamp01NansTo0(UnitFloat) * 255.f );
        }
        
        static uint16 QuantizeUNormFloatTo16( float UnitFloat )
        {
            return (uint16)( 0.5f + FLinearColor::Clamp01NansTo0(UnitFloat) * 65535.f );
        }

        static float DequantizeUNorm8ToFloat( int Value8 )
        {
            return (float)Value8 * (1.f/255.f);
        }
        
        static float DequantizeUNorm16ToFloat( int Value16 )
        {
            return (float)Value16 * (1.f/65535.f);
        }

        static uint8 Requantize10to8( int Value10 )
        {
            // Dequantize from 10 bit (Value10/1023.f)
            // requantize to 8 bit with rounding (GPU convention UNorm)
            //  this is the computation we want :
            // (int)( (Value10/1023.f)*255.f + 0.5f );
            // this gives the exactly the same results :
            int Temp = Value10*255 + (1<<9);
            int Value8 = (Temp + (Temp >> 10)) >> 10;
            return (uint8)Value8;
        }
        
        static uint8 Requantize16to8(int Value16)
        {
            // Dequantize x from 16 bit (Value16/65535.f)
            // then requantize to 8 bit with rounding (GPU convention UNorm)

            // matches exactly with :
            //  (int)( (Value16/65535.f) * 255.f + 0.5f );
            int Value8 = (Value16*255 + 32895)>>16;
            return (uint8)Value8;
        }

        /**
        * Return 8-bit color Quantized from 10-bit RGB , 2-bit A
        */
        static Color MakeRequantizeFrom1010102( int R, int G, int B, int A )
        {
            // requantize 2 bits to 8 ; could bit-replicate or just table lookup :
            const uint8 Requantize2to8[4] = { 0, 0x55, 0xAA, 0xFF };
            return Color(
                Requantize10to8(R),
                Requantize10to8(G),
                Requantize10to8(B),
                Requantize2to8[A] );

        }

        /**
         *	@return a new Color based of this color with the new alpha value.
        *	Usage: const Color& MyColor = ColorList::Green.WithAlpha(128);
        */
        Color WithAlpha( uint8 Alpha ) const
        {
            return Color( R, G, B, Alpha );
        }

        /**
         * Reinterprets the color as a linear color.
         * This is the correct dequantizer for QuantizeRound.
         * This matches the GPU spec conversion for U8<->float
         * @return The linear color representation.
         */
        FORCEINLINE FLinearColor ReinterpretAsLinear() const
        {
            constexpr float inv255 = 1.f / 255.f;
            return FLinearColor(R * inv255, G * inv255, B * inv255, A * inv255);
        }

        /**
         * Gets the color in a packed uint32 format packed in the order ARGB.
         */
        FORCEINLINE uint32 ToPackedARGB() const
        {
            return ( A << 24 ) | ( R << 16 ) | ( G << 8 ) | ( B << 0 );
        }

        /**
         * Gets the color in a packed uint32 format packed in the order ABGR.
         */
        FORCEINLINE uint32 ToPackedABGR() const
        {
            return ( A << 24 ) | ( B << 16 ) | ( G << 8 ) | ( R << 0 );
        }

        /**
         * Gets the color in a packed uint32 format packed in the order RGBA.
         */
        FORCEINLINE uint32 ToPackedRGBA() const
        {
            return ( R << 24 ) | ( G << 16 ) | ( B << 8 ) | ( A << 0 );
        }

        /**
         * Gets the color in a packed uint32 format packed in the order BGRA.
         */
        FORCEINLINE uint32 ToPackedBGRA() const
        {
            return ( B << 24 ) | ( G << 16 ) | ( R << 8 ) | ( A << 0 );
        }

        /** Some pre-inited colors, useful for debug code */
        static const Color White;
        static const Color Black;
        static const Color Transparent;
        static const Color Red;
        static const Color Green;
        static const Color Blue;
        static const Color Yellow;
        static const Color Cyan;
        static const Color Magenta;
        static const Color Orange;
        static const Color Purple;
        static const Color Turquoise;
        static const Color Silver;
        static const Color Emerald;

        friend FORCEINLINE uint32 GetTypeHash( const Color& Color )
        {
            return Color.DWColor();
        }

    private:
        /**
         * Please use .ToColor(true) on FLinearColor if you wish to convert from FLinearColor to Color,
         * with proper sRGB conversion applied.
         *
         * Note: Do not implement or make public.  We don't want people needlessly and implicitly converting between
         * FLinearColor and Color.  It's not a free conversion.
         */
        explicit Color(const FLinearColor& LinearColor);
    };
}